package deepbrain.simnetwork.util;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import deepbrain.simnetwork.engine.SimEngine;
import deepbrain.simnetwork.engine.SimpleSimEngine;
import deepbrain.simnetwork.exception.SimNetworkRuntimeException;
import deepbrain.simnetwork.loader.NetworkLoader;
import deepbrain.simnetwork.loader.xml.XmlNetworkLoader;
import deepbrain.simnetwork.monitor.DisplayNetworkMonitor;
import deepbrain.simnetwork.monitor.SimTaskMonitor;
import deepbrain.simnetwork.network.Network;
import deepbrain.simnetwork.network.NetworkState;
import deepbrain.simnetwork.network.NodeState;
import deepbrain.simnetwork.task.SimulationTask;

public class SimNetUtils {

	public static final ExecutorService threadPool = Executors
			.newCachedThreadPool();

	public static final Random random = new Random(System.currentTimeMillis());
	public static final HashMap<Integer, List<NodeState[]>> stateCaches = new HashMap<Integer, List<NodeState[]>>();

	public static List<NetworkState> getAllCertainNetworkStates(int numOfNodes) {
		List<NetworkState> list = new ArrayList<NetworkState>();
		NodeState[] states = new NodeState[numOfNodes];
		dfs0(list, states, 0, numOfNodes);
		return list;
	}

	public static List<NodeState[]> getAllCertainStates(int numOfNodes) {
		if (stateCaches.containsKey(numOfNodes)) {
			return stateCaches.get(numOfNodes);
		} else {
			List<NodeState[]> list = new ArrayList<NodeState[]>();
			NodeState[] states = new NodeState[numOfNodes];
			dfs(list, states, 0, numOfNodes);
			stateCaches.put(numOfNodes, list);
			return list;
		}
	}

	private static void dfs0(List<NetworkState> list, NodeState[] states,
			int depth, int numOfNodes) {
		if (depth >= numOfNodes) {
			NodeState[] copy = Arrays.copyOf(states, numOfNodes);
			list.add(new NetworkState(copy));
			return;
		}
		states[depth] = NodeState.CERTAIN_SILENT;
		dfs0(list, states, depth + 1, numOfNodes);
		states[depth] = NodeState.CERTAIN_FIRING;
		dfs0(list, states, depth + 1, numOfNodes);
	}

	private static void dfs(List<NodeState[]> list, NodeState[] states,
			int depth, int numOfNodes) {
		if (depth >= numOfNodes) {
			NodeState[] copy = Arrays.copyOf(states, numOfNodes);
			list.add(copy);
			return;
		}
		states[depth] = NodeState.CERTAIN_SILENT;
		dfs(list, states, depth + 1, numOfNodes);
		states[depth] = NodeState.CERTAIN_FIRING;
		dfs(list, states, depth + 1, numOfNodes);
	}

	public static PrintWriter getFileWriter(String filePath) {
		try {
			return new PrintWriter(new FileWriter(filePath));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static double random() {
		return random.nextDouble();
	}

	public static void runNetwork(String filePath, NetworkState initState,
			final int steps) {
		try {
			NetworkLoader loader = new XmlNetworkLoader(filePath);
			Network network = loader.loadNetwork();
			System.out.println(network);
			SimulationTask task = new SimulationTask(network, initState) {
				@Override
				public void runTask() {
					runForSteps(steps);
				}
			};

			task.addMonitor(new DisplayNetworkMonitor(System.out));

			SimEngine engine = new SimpleSimEngine();
			engine.runTask(task);
		} catch (Exception e) {
			throw new SimNetworkRuntimeException(e);
		}
	}

	public static void runNetwork(String filePath, final int steps,
			Iterable<SimTaskMonitor> monitors) {
		try {
			NetworkLoader loader = new XmlNetworkLoader(filePath);
			Network network = loader.loadNetwork();
			System.out.println(network);
			SimulationTask task = new SimulationTask() {
				@Override
				public void runTask() {
					runForSteps(steps);
				}
			};
			task.setNetwork(network);

			task.addMonitor(new DisplayNetworkMonitor(System.out));
			for (SimTaskMonitor monitor : monitors)
				task.addMonitor(monitor);

			SimEngine engine = new SimpleSimEngine();
			engine.runTask(task);
		} catch (Exception e) {
			throw new SimNetworkRuntimeException(e);
		}
	}

}
